package br.unifor.ed.tads;

import br.unifor.ed.exceptions.InvalidPositionException;
import br.unifor.ed.exceptions.ListEmptyException;
import br.unifor.ed.exceptions.ListFullException;
import br.unifor.ed.nodes.SingleNode;

public class GenericMemoryLinkedBasedList<E> implements MyGenericList<E> {
	private SingleNode<E> first;
	private SingleNode<E> last;
	private int elementCounter;

	public GenericMemoryLinkedBasedList() {
		super ();
		setFirst ( null );
		setLast  ( null );
		setElementCounter( 0 );
	}

	public boolean isEmpty() {
		return ( ( getFirst() == null ) &&
				 ( getLast () == null ) );
//		return ( size () == 0 );
	}

	public boolean isFull() {
		return false;
	}

	public int size() {
		return getElementCounter();
	}

	public void print() throws ListEmptyException {
		if ( isEmpty() )
			throw new ListEmptyException( this.getClass().getName() );
		
		for ( SingleNode<E> cursor = getFirst(); cursor != null; cursor = cursor.getNext() )
			System.out.println( cursor.getData() );
	}

	public void addFirst(E newData) throws ListFullException {
		if ( isFull() )
			throw new ListFullException( this.getClass().getName() );
		
		SingleNode<E> newNode = new SingleNode<E> ( newData, getFirst() );
		
		if ( isEmpty() )
			setLast ( newNode );
		setFirst ( newNode );
		
		incrementElementCounter ();
	}

	public void addLast(E newData) throws ListFullException {
		if ( isFull () )
			throw new ListFullException ( this.getClass().getName() );
		
		SingleNode<E> newNode = new SingleNode<E> ( newData );
		
		if ( isEmpty() )
			setFirst ( newNode );
		setLast ( newNode );
		
		incrementElementCounter ();
	}

	public void addAt(int position, E newData) throws ListFullException,
			InvalidPositionException {
		if ( isFull () )
			throw new ListFullException( this.getClass().getName() );
		
		if ( ( position < 1 ) || ( position > size() + 1 ) )
			throw new InvalidPositionException( position );
		
		if ( position == 1 )
			addFirst ( newData );
		else if ( position == size() + 1 )
			addLast ( newData );
		else {
			SingleNode<E> newNode = new SingleNode<E> ( newData ),
					      cursor  = getFirst ();
			for ( int i = 2; i < position; i++ )
				cursor = cursor.getNext ();
			
			newNode.setNext ( cursor.getNext() );
			cursor.setNext ( newNode );
			
			incrementElementCounter ();
		}
	}

	public E removeFirst() throws ListEmptyException {
		if ( isEmpty() )
			throw new ListEmptyException( this.getClass().getName() );
		
		E removedData = getFirst().getData();
		setFirst( getFirst().getNext() );
		decrementElementCounter ();
		
		return removedData;
	}

	public E removeLast() throws ListEmptyException {
		if ( isEmpty() )
			throw new ListEmptyException( this.getClass().getName() );
		
		SingleNode<E> cursor = getFirst ();
		for ( int i = 2; i < size (); i++ )
			cursor = cursor.getNext();
		
		E removedData = getLast().getData();
		cursor.setNext( cursor.getNext().getNext() );
		decrementElementCounter();
		
		return removedData;
	}

	public E removeAt(int position) throws ListEmptyException,
			InvalidPositionException {
		if ( isEmpty() )
			throw new ListEmptyException( this.getClass().getName() );
		
		if ( ( position < 1 ) || ( position > size () ) )
			throw new InvalidPositionException( position );
		
		if ( position == 1 )
			return removeFirst();
		else if ( position == size () )
			return removeLast();
		else {
			SingleNode<E> cursor = getFirst();
			for ( int i = 2; i < position; i++ )
				cursor = cursor.getNext();
			
			E removedData = cursor.getNext().getData();
			cursor.setNext( cursor.getNext().getNext() );
			decrementElementCounter();
			
			return removedData;
		}
	}
	
	public E get(int position) throws ListEmptyException,
	InvalidPositionException {
		if ( isEmpty() )
			throw new ListEmptyException( this.getClass().getName() );
		
		if ( ( position < 1 ) || ( position > size () ) )
			throw new InvalidPositionException ( position );
		
		if ( position == 1 )
			return ( getFirst().getData() );
		else if ( position == size () )
			return ( getLast().getData() );
		else {
			SingleNode<E> cursor = getFirst (); 
			for ( int i = 2; i <= position; i++ )
				cursor = cursor.getNext();
			return cursor.getData();
		}
	}
	
	private void decrementElementCounter() {
		setElementCounter( getElementCounter() - 1 );
	}

	private void incrementElementCounter () {
		setElementCounter ( getElementCounter() + 1 );
	}

	private SingleNode<E> getFirst() {
		return first;
	}

	private void setFirst(SingleNode<E> first) {
		this.first = first;
	}

	private SingleNode<E> getLast() {
		return last;
	}

	private void setLast(SingleNode<E> last) {
		this.last = last;
	}

	private int getElementCounter() {
		return elementCounter;
	}

	private void setElementCounter(int elementCounter) {
		this.elementCounter = elementCounter;
	}
}
